package com.ilia.slitok;

import static com.ilia.slitok.Constants.CMD_OUT_FILE;
import static com.ilia.slitok.Constants.CMD_PAUSE;
import static com.ilia.slitok.Constants.CMD_SOURCE_BARS;
import static com.ilia.slitok.Constants.CMD_TARGET_BARS;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import com.ilia.util.config.ConfigurationProperty;
import com.ilia.util.timer.ITimer;
import com.ilia.util.timer.TimerFactory;

public class Traverse {

	public static String nr = "\n";

	boolean doPause = false;
	int pause = 0;

	long last_dot_output_time = 0;
	long last_status_output_time = 0;

	PolyAlloyStorage poly_alloys_storage;

	List<Alloy> found_alloys;
	int found_alloys_size;

	int DOT_OUTPUT_PERIOD = 10000;
	int STATUS_OUTPUT_PERIOD = 30000;

	boolean immediatelyExit = false;

	String tmpName = null;

	PolyAlloyTargetAlloyComparator comparator;

	@ConfigurationProperty(name = "timer.name", type = "string")
	String timerClassName = null;

	ITimer timer;

	long started_at, finished_at;

	List<Alloy> source_bars;
	List<Alloy> target_bars;
	Alloy target_bar;

	@ConfigurationProperty(name = "file.output", type = "string")
	String outputName = null;

	
	@ConfigurationProperty(name = "poly-alloy-siblings", type = "int")
	public static int POLY_ALLOY_SIBLINGS = 10;

	@ConfigurationProperty(name = "target-bar-id", type = "int")
	int target_bar_id = 0;

	@ConfigurationProperty(name = "generation-size", type = "int")
	int generation_size = 1000;

	@ConfigurationProperty(name = "iteration-count", type = "int")
	int iteration_count = 4;

	@ConfigurationProperty(name = "grid-size", type = "int")
	int grid_size = 100;

	@ConfigurationProperty(name = "set-size", type = "int")
	int max_bar_set_size;

	@ConfigurationProperty(name = "storage-name", type = "string")
	String storage_name = "PolyAlloyStorageSortedSetImpl";

	@ConfigurationProperty(name = "storage4-size", type = "int")
	int storage4_size = 1000;

	int target_match;

	int[] current_bar_set;
	int current_bar_set_size;

	int source_bar_count; // number of bars
	int max_bar_id;
	int target_bar_count; // number of bars

	private Factory factory;

	boolean isUp() {
		timer.startTimer("IS_UP");
		boolean result = isUp_internal();
		timer.stopTimer("IS_UP");
		return result;
	}

	boolean isUp_internal() {
		if (immediatelyExit)
			return false;
		boolean bool = (current_bar_set_size >= max_bar_set_size);
		if (bool)
			return false;
		if (current_bar_set_size > 0) {
			int lastPiece = current_bar_set[current_bar_set_size - 1];
			if (lastPiece == max_bar_id) {
				return false;
			}
		}
		return true;
	}

	public Traverse(String fileName) {
		configure(fileName, this);
	}

	private String[] parseFileName(String filePath) {
		File f = new File(filePath);
		String ap = f.getAbsolutePath();
		String fileName = f.getName();
		File parentFile = f.getParentFile();
		String parent = parentFile.getAbsolutePath();

		String name = null;
		String ext = null;
		int dotIndex = fileName.indexOf('.');
		if (dotIndex > 1) {
			name = fileName.substring(0, dotIndex);
			ext = fileName.substring(dotIndex + 1, fileName.length());
		}

		String[] str = new String[4];
		str[0] = ap;
		str[1] = parent;
		str[2] = name;
		str[3] = ext;
		return str;
	}

	boolean isRight() {
		if (immediatelyExit)
			return false;
		int lastPiece = current_bar_set[current_bar_set_size - 1];

		if (lastPiece == max_bar_id) {
			return false;
		}
		// if
		// (lastPiece==source_bar_count-(max_bar_set_size-current_bar_set_size)-1)
		// {
		// return false;
		// }
		return true;
	}

	boolean isDown() {
		return (current_bar_set_size > 0);
	}

	void up() {
		int lastPiece = -1;
		if (current_bar_set_size > 0) {
			lastPiece = current_bar_set[current_bar_set_size - 1];
		}

		int newPiece = lastPiece + 1;
		current_bar_set[current_bar_set_size] = newPiece;
		current_bar_set_size++;
	}

	static String print_chars(String name, char[] chars) {
		StringBuffer sb = new StringBuffer();
		sb.append(name + "=[");
		for (int i = 0; i < chars.length; i++) {
			sb.append(chars[i]);
		}
		sb.append("]");
		return sb.toString();
	}

	void right() {
		timer.startTimer("RIGHT");
		int lastPiece = current_bar_set[current_bar_set_size - 1];
		int newPiece = lastPiece + 1;
		current_bar_set[current_bar_set_size - 1] = newPiece;
		timer.stopTimer("RIGHT");
	}

	void down() {
		current_bar_set_size--;
	}

	void UV() {
		timer.startTimer("UV");
		while (isUp()) {
			up();
			work();
		}
		timer.stopTimer("UV");
	}

	void init() {
		started_at = System.currentTimeMillis();
		current_bar_set_size = 0;
		current_bar_set = new int[max_bar_set_size];
		last_dot_output_time = System.currentTimeMillis();
		last_status_output_time = System.currentTimeMillis();

		found_alloys = new ArrayList<Alloy>();
		found_alloys_size = 0;

		factory = new Factory();
		target_bar = target_bars.get(target_bar_id);

		poly_alloys_storage = factory.createSortedSetImpl(storage_name, timer,
				target_bar.coord);

		StringBuffer sb = new StringBuffer();
		sb.append(commandString(CMD_SOURCE_BARS, "" + source_bar_count) + nr);
		for (Alloy alloy : source_bars) {
			sb.append(alloy.toString() + nr);
		}

		sb.append(commandString(CMD_TARGET_BARS, "" + target_bar_count) + nr);
		for (Alloy alloy : target_bars) {
			sb.append(alloy.toString() + nr);
		}
		save(outputName, sb.toString(), false);
	}

	private String commandString(String cmd, String value) {
		return cmd + "=" + value;
	}

	private void userCommand(byte command) {
		char c = (char) command;
		switch (c) {
		case 'e':
			immediatelyExit = true;
			break;
		case 'p':
			save(outputName, statusToString(), true);
			break;
		case ' ':
			doPause = true;
			break;
		case '?':
		case 'h':
			System.out
					.println("e - exit, p - print, d - on/off dots, SPACE-pause, ?,h - this screen");
			break;

		}
	}

	void process_user_input() {
		if (System.currentTimeMillis() - DOT_OUTPUT_PERIOD >= last_dot_output_time) {
			last_dot_output_time = System.currentTimeMillis();

			try {
				byte[] bytes = new byte[1];
				int av = System.in.available();
				if (av > 0) {
					int read2 = System.in.read(bytes);
					if (read2 > 0) {
						userCommand(bytes[0]);
					}
					System.in.skip(System.in.available());
				}
			} catch (IOException e) {
			}
		}
	}

	void work() {
		process_user_input();
		if (doPause) {
			System.out.println("pause. press ENTER to continue");
			try {
				System.in.skip(System.in.available());
			} catch (IOException e) {
			}
			doPause = false;

		}
		if (pause > 0) {
			try {
				Thread.yield();
				Thread.sleep(pause);
			} catch (InterruptedException e) {
			}
		}
		if (current_bar_set_size > 0) {
			timer.startTimer("ADD_MOVE");
			Alloy alloy = new Alloy(0, Arrays.copyOf(current_bar_set,
					current_bar_set_size), source_bars);
			found_alloys_size++;
			found_alloys.add(alloy);
			PolyAlloy pa = PolyAlloy.createList(alloy,
					PolyAlloy.createBox(alloy, grid_size));
			poly_alloys_storage.add(pa);
			timer.stopTimer("ADD_MOVE");
		}
	}

	private String alloyToString(int[] alloy, int alloy_size) {
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < alloy_size; i++) {
			if (i > 0)
				sb.append(" ");
			sb.append(alloy[i]);
		}
		return sb.toString();
	}

	private String statusToString() {
		StringBuffer sb = new StringBuffer();
		long total_time = (System.currentTimeMillis() - started_at) / 1000;

		sb.append(commandString(CMD_OUT_FILE, "" + outputName) + nr);

		sb.append(commandString("CURRENT_ALLOY",
				"" + alloyToString(current_bar_set, current_bar_set_size))
				+ nr);

		sb.append(commandString("FOUND_ALLOYS", "" + found_alloys_size) + nr);
		// sb.append(foundMovesToString(found_alloys));
		sb.append(nr);

		sb.append(commandString("POLY_ALLOYS",
				"" + poly_alloys_storage.getSize())
				+ nr);
		// sb.append(commandString("POLY_ALLOYS2", "" +
		// poly_alloys_storage2.getSize()) + nr);
		sb.append(commandString("TARGET_MATCH", "" + target_match) + nr);

		sb.append("time= " + total_time + " sec" + nr);
		sb.append("timer= " + timer.toString() + nr);

		return sb.toString();
	}

	public void finish(String filename, boolean append) {
		finished_at = System.currentTimeMillis();
		String s = statusToString();
		save(filename, s, append);
	}

	public void save(String fileName, String content, boolean append) {
		try {
			System.out.println(content);
			FileWriter fw = new FileWriter(fileName, append);
			BufferedWriter bw = new BufferedWriter(fw);
			bw.write(content);
			bw.flush();
			bw.close();
		} catch (IOException ex) {
			System.out.println("error while saving file");
		}

	}

	void get_initial_alloys() {
		timer.startTimer("INITIAL_ALLOY");
		work();
		UV();
		while (isDown()) {
			if (isRight()) {
				right();
				work();
				UV();
			} else {
				down();
				// work();
			}
		}
		timer.stopTimer("INITIAL_ALLOY");
	}

	PolyAlloyStorage iter2(PolyAlloyStorage sourceStorage) {
		timer.startTimer("ITER2");
		PolyAlloyStorage storage = factory.createSortedSetImpl(storage_name,
				timer, target_bar.coord);
		for (PolyAlloy pa : sourceStorage.values()) {
			storage.add(PolyAlloy.cloneList(pa));
		}
		for (PolyAlloy pa : sourceStorage.values()) {
			List<Alloy> apas = (List<Alloy>) pa.getAlloys();
			for (Alloy apa : apas) {
				for (Alloy alloy : source_bars) {
					Alloy newAlloy = Alloy.combineNew(alloy, apa);
					if (newAlloy != null) {
						storage.add(PolyAlloy.createList(newAlloy,
								PolyAlloy.createBox(newAlloy)));
					}
				}
			}
		}

		timer.stopTimer("ITER2");
		return storage;
	}

	PolyAlloyStorage iter3(PolyAlloyStorage sourceStorage) {
		timer.startTimer("ITER3");
		PolyAlloyStorage storage = factory.createSortedSetImpl(storage_name,
				timer, target_bar.coord);
		for (PolyAlloy pa : sourceStorage.values()) {
			storage.add(PolyAlloy.cloneList(pa));
		}
		for (PolyAlloy pa : sourceStorage.values()) {
			List<Alloy> apas = (List<Alloy>) pa.getAlloys();
			for (Alloy apa : apas) {
				for (Alloy alloy : source_bars) {
					Alloy newAlloy = Alloy.combineNew(alloy, apa);
					if (newAlloy != null) {
						storage.add(PolyAlloy.createList(newAlloy,
								PolyAlloy.createBox(newAlloy)));
					} else {
						Alloy minusoneAlloy = Alloy.xorNew(apa, alloy);
						minusoneAlloy.size=apa.size-1;
						for (Alloy alloy2 : source_bars) {
							Alloy newAlloy2 = Alloy.combineNew(minusoneAlloy,
									alloy2);
							if (newAlloy2 != null) {
								storage.add(PolyAlloy.createList(newAlloy2,
										PolyAlloy.createBox(newAlloy2)));
							}
						}
					}
				}
			}

			if (storage.getSize() > 2 * generation_size) {
				storage.trunc_and_pack(generation_size, false);
			}

			process_user_input();
		}

		timer.stopTimer("ITER3");
		return storage;
	}

	PolyAlloyStorage iter4(PolyAlloyStorage sourceStorage) {
		timer.startTimer("ITER4");
		PolyAlloyStorage storage = factory.createSortedSetImpl(storage_name,
				timer, target_bar.coord);
		for (PolyAlloy pa : sourceStorage.values()) {
			storage.add(PolyAlloy.cloneList(pa));
		}
		for (PolyAlloy pa : sourceStorage.values()) {
			List<Alloy> apas = (List<Alloy>) pa.getAlloys();
			for (Alloy apa : apas) {
				for (Alloy alloy : source_bars) {
					Alloy newAlloy = Alloy.combineNew(alloy, apa);
					if (newAlloy != null) {
						storage.add(PolyAlloy.createList(newAlloy,
								PolyAlloy.createBox(newAlloy)));
					} else {
						Alloy minusoneAlloy = Alloy.xorNew(apa, alloy);
						storage.add(PolyAlloy.createList(minusoneAlloy,
								PolyAlloy.createBox(minusoneAlloy)));
					}
				}
			}

			if (storage.getSize() > 2 * generation_size) {
				storage.trunc_and_pack(generation_size, false);
			}
		}

		timer.stopTimer("ITER4");
		return storage;
	}

	void tenBest(String msg, PolyAlloyStorage storage) {

		Alloy target_bar = target_bars.get(target_bar_id);
		PolyAlloyTargetAlloyComparator comparator = new PolyAlloyTargetAlloyComparator(
				target_bar.coord);

		int MAX = 1;
		int m = 1;
		StringBuffer sb = new StringBuffer();
		sb.append(msg + nr);
		for (PolyAlloy pa : storage.sorted_values(MAX)) {
			List<Alloy> list = (List<Alloy>) pa.getAlloys();
			pa.pack();
			for (Alloy alloy : list) {
				int dist = comparator.distance(alloy.coord);
				sb.append("   " + dist + ":\t" + alloy.toString() + nr);
			}
			m++;
			if (m >= MAX)
				break;
		}
		save(outputName, sb.toString(), true);
	}

	void traverse() {
		timer = TimerFactory.createTimer(timerClassName);
		timer.startTimer("ALL");
		init();
		get_initial_alloys();

		PolyAlloyStorage oldstorage;
		PolyAlloyStorage newstorage;

		int cnt = 1;

		oldstorage = (PolyAlloyStorage) poly_alloys_storage;
		while (cnt <= iteration_count) {
			newstorage = iter3(oldstorage);
			newstorage.trunc_and_pack(generation_size, true);
			oldstorage = newstorage;
			tenBest("result " + cnt, oldstorage);

			cnt++;
		}

		tenBest("result", oldstorage);

		timer.stopTimer("ALL");
		if (immediatelyExit) {
			finish(tmpName, true);
		} else {
			finish(outputName, true);
		}
	}

	String moves_to_string(char[] chars) {
		StringBuffer sb = new StringBuffer();
		sb.append(new String(chars));
		return sb.toString();

	}

	public static void main(String[] args) {

		if (args.length == 1) {
			Traverse t = new Traverse(args[0]);
			t.traverse();
		} else {
			System.out.println("specify config file as parameter!");
		}

	}

	public void configure(String fileName, Object objectToConfigure) {
		String config = readConfigFile(fileName);
		String[] configPathes = parseFileName(fileName);
		String parentFile = configPathes[1];
		outputName = parentFile + File.separator + configPathes[2]
				+ ".result.txt";
		tmpName = parentFile + File.separator + configPathes[2] + ".tmp";

		StringTokenizer st = new StringTokenizer(config, nr);
		timerClassName = "com.ilia.util.timer.impl.EmptyTimer";

		boolean sourceBarRecords = false;
		boolean targetBarRecords = false;
		source_bars = new ArrayList<Alloy>();
		target_bars = new ArrayList<Alloy>();

		Map<String, Field> configProps = new HashMap<String, Field>();
		Field[] fields = objectToConfigure.getClass().getDeclaredFields();
		for (Field field : fields) {
			ConfigurationProperty prop = field
					.getAnnotation(ConfigurationProperty.class);
			if (prop != null) {
				configProps.put(prop.name(), field);
			}
		}

		int cntr = 0;
		while (st.hasMoreElements()) {
			String str = st.nextToken();
			if (sourceBarRecords || targetBarRecords) {
				if (!"".equals(str.trim())) {
					if (sourceBarRecords) {
						Alloy bar = Alloy.createFromString(str,
								source_bar_count);
						source_bars.add(bar);
						if (cntr == source_bar_count - 1) {
							sourceBarRecords = false;
						}
					}
					if (targetBarRecords) {
						Alloy bar = Alloy.createFromString(str,
								target_bar_count);
						target_bars.add(bar);
						if (cntr == target_bar_count - 1) {
							targetBarRecords = false;
						}
					}
					cntr++;
				}
			} else {
				StringTokenizer st2 = new StringTokenizer(str, "=");
				String name = null;
				String value = null;
				while (st2.hasMoreTokens()) {
					String token = st2.nextToken();
					if (name == null) {
						name = token.trim();
					} else if (value == null) {
						value = token.trim();
					}
				}
				if (name != null) {
					try {
						if (configProps.containsKey(name)) {
							Field field = configProps.get(name);
							ConfigurationProperty prop = field
									.getAnnotation(ConfigurationProperty.class);
							if ("int".equals(prop.type())) {
								int int_value = Integer.parseInt(value);
								field.set(objectToConfigure, new Integer(
										int_value));
							} else {
								field.set(objectToConfigure, value);
							}

						}
					} catch (IllegalArgumentException e) {
						e.printStackTrace();
					} catch (IllegalAccessException e) {
						e.printStackTrace();
					}
					if (CMD_PAUSE.equalsIgnoreCase(name)) {
						pause = Integer.parseInt(value);
					} else if (CMD_SOURCE_BARS.equalsIgnoreCase(name)) {
						sourceBarRecords = true;
						cntr = 0;
						source_bar_count = Integer.parseInt(value);
						max_bar_id = source_bar_count - 1;
					} else if (CMD_TARGET_BARS.equalsIgnoreCase(name)) {
						targetBarRecords = true;
						cntr = 0;
						target_bar_count = Integer.parseInt(value);
					}
				}
			}
		}

	}

	public String readConfigFile(String file_name) {
		try {
			BufferedReader in = new BufferedReader(new FileReader(file_name));
			StringBuffer sb = new StringBuffer();
			String ins = in.readLine();
			while (ins != null) {
				sb.append(ins);
				sb.append(nr);
				ins = in.readLine();
			}
			String config = sb.toString();
			in.close();
			return config;
		} catch (FileNotFoundException ex) {
			System.out.println("file not found " + file_name);
		} catch (IOException ex1) {
			System.out.println("error reading input file " + file_name);
		}
		return null;

	}

	class Factory {

		public PolyAlloyStorage createSortedSetImpl(String name, ITimer timer,
				int[] target_coord) {
			if ("PolyAlloyStorageSortedSetImpl".equals(name)) {
				return new PolyAlloyStorageSortedSetImpl(timer, target_coord);
			} else if ("Storage4".equals(name)) {
				return new Storage4(storage4_size,
						new PolyAlloyTargetAlloyComparator(target_coord), timer);
			}
			return null;
		}
	}

}
